#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>
#include <mutex>
#include "util1.hpp"

namespace ns_index
{
    struct Docinfo
    {
        std::string title;   
        std::string content; 
        std::string url;     
        uint64_t doc_id;  //文档id        
    };

    struct InvertedElem
    {
        uint64_t doc_id;
        std::string word;
        int weight;
        InvertedElem():weight(0){}
    };

    typedef std::vector<InvertedElem> InvertedList;
    //构建索引，根据关键字获取到id，然后再根据id获取到内容
    //关键字找到id为倒排索引，根据id找到内容为正排索引，索索首先是根据关键字找到id再执行下一步
    //正排索引用容器vector来构建，数组下标就是最好的文档id

    class Index
    {
        private:
            std::vector<Docinfo> forword_index;//正排索引
            std::unordered_map<std::string, InvertedList> inverted_index;//倒排索引，根据关键字找到文档，找到的文档会以网页排列
            //网页搜索结果按照权重来排版
        private:
            Index(){} //但是一定要有函数体，不能delete
            Index(const Index&) = delete;
            Index& operator=(const Index&) = delete;

            static Index* instance;
            static std::mutex mtx;
        public:
            ~Index(){}
        public:
            //获取单例，要加锁保护
            static Index* GetInstance()
            {
                if(nullptr == instance)
                {
                    mtx.lock();
                    if(nullptr == instance)
                    {
                        instance = new Index();
                    }
                    mtx.unlock();
                }
                return instance;
            }
        public:
            //正排索引:根据文档Id找到对应文档
            Docinfo *GetForword(uint64_t doc_id)
            {
                if(doc_id >= forword_index.size())
                {
                        std::cerr << "doc_id out range, error!" << std::endl;
                        return nullptr;
                }

                return &forword_index[doc_id];
            }

            //倒排索引：根据关键字查找所在文档
            InvertedList* GetInvertedList(std::string &word)
            {
                auto iter = inverted_index.find(word);

                if(iter == inverted_index.end())
                {
                    std::cerr << word << " have no InvertedList" << std::endl;
                    return nullptr;
                }

                return &(iter->second);
            }

            //去标签之后的文档构建正排和倒排索引
            //将所有文档获取之后构建一个个索引
            bool BuildIndex(const std::string &input)
            {
                std::ifstream in(input, std::ios::in | std::ios::binary);
                if(!in.is_open())
                {
                    std::cerr << "sorry, " << input << " open error" << std::endl;
                    return false;
                }

                std::string line;
                int count = 0;
                while(std::getline(in, line))
                {
                    Docinfo * doc = BuildForwardIndex(line);
                    if(nullptr == doc)
                    {
                        std::cerr << "build " << line << " error" << std::endl; //for deubg
                        continue;
                    }

                    //根据正排索引的网页文档，然后进行倒排索引，返回一个网页资源（与关键字相关的标题、有关键字的摘要、url)
                    BuildInvertedIndex(*doc);
                    count++;
                    if(count % 50 == 0)
                    {
                        //建立索引是一个漫长的过程，打印查看索引建立进度
                        std::cout <<"当前已经建立的索引文档: " << count <<std::endl;
                        //LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
                    }
                }
                return true;

            }

    private:
        //构建正排索引，文档id知道之后，然后在这一个文档中获取与关键字有关的title,摘要，url
        //输入搜索，然后得到id，然后再在文档中查找，对输入的内容进行切分为多个关键字
        //将文档内容保存在string中的，所以要将字符串分解为三部分，title,content,url
        Docinfo *BuildForwardIndex(const std::string &line)
        {
            //1. 解析line，将字符串切分三部分，三部分构成一个个网页
            //line -> 3 string, title, content, url
            std::vector<std::string> results;
            const std::string sep = "\3";   //行内分隔符
            //而切分字符串使用boost库中的split进行切分，将切分的内容保存到result中
            ns_util::StringUtil::Split(line, &results, sep); //将string按照\3进行切割，保存到results容器中
            //ns_util::StringUtil::CutString(line, &results, sep);
            if(results.size() != 3)
            {
                return nullptr;
            }

            //2. 切分好的字符串进行填充到DocIinfo，网页三部分
            Docinfo doc;
            doc.title = results[0];    //title
            doc.content = results[1];  //content
            doc.url = results[2];      ///url
            doc.doc_id = forword_index.size(); //先进行保存id，在插入，对应的id就是当前doc在vector中的下标!

            //3. 插入到正排索引的vector
            forword_index.push_back(std::move(doc)); //doc,html文件内容
            return &forword_index.back();            //每次返回最新保存的网页文档
        }

        //构建索引查找时返回的网页按照权重排列，所以要统计每个网页关键字出现的次数
        bool BuildInvertedIndex(const Docinfo &doc)
        {
            //DocInfo{title, content, url, doc_id}
            //word -> 倒排拉链

            //网页返回时会有一个排序结果，排序结果是根据关键字出现的次数排序，频次多的出现在首位。依次列举网页，而搜索的关键字
            //在标题和内容中都会出现，所以标题和内容分开统计，并且标题的频次要高一点，相关性更强一点
            struct word_cnt
            {
                int title_cnt;
                int content_cnt;

                word_cnt():title_cnt(0), content_cnt(0){}
            };
            std::unordered_map<std::string, word_cnt> word_map; //用来暂存词频的映射表

            //搜索内容进行分词，分成可能的多个关键词，然后再在全网进行网页获取返回
            //对标题进行分词
            std::vector<std::string> title_words;   //分词结果保存
            ns_util::JiebaUtil::CutString(doc.title, &title_words);

            //if(doc.doc_id == 1572)
            //{
            //    for(auto &s : title_words){
            //        std::cout << "title: " << s << std::endl;
            //    }
            //}

            //逐一分词：对一个网页文档进行分词，所有的进行分词，然后再搜索时才能根据分词来进行索引
            //然后再对分出来的词进行统计(权重)
            //对标题进行词频统计
            for(std::string s : title_words)
            {
                boost::to_lower(s);      //搜索结果不区分大小写，需要统一转化成为小写
                word_map[s].title_cnt++; //[]如果存在就获取，如果不存在就新建
            }

            //对文档内容进行分词
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc.content, &content_words);

            //对内容进行词频统计
            for(std::string s : content_words)
            {
                boost::to_lower(s);
                word_map[s].content_cnt++;
            }

#define X 10
#define Y 1
            for(auto &word_pair : word_map)
            {
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = word_pair.first;
                item.weight = X*word_pair.second.title_cnt + Y*word_pair.second.content_cnt; //权重(相关性)
                InvertedList &inverted_list = inverted_index[word_pair.first];
                inverted_list.push_back(std::move(item));
            }

            return true;
        }
    };

    Index* Index::instance = nullptr;
    std::mutex Index::mtx;
}
